package com.lcm.weam.util;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

/**
 * 时序灰度预测算法工具类
 */
public class GMUtil {

    //预测结果
    public static double[] doAlgo(double[] data, int t) {
        double[] res = new double[data.length + t];
        for (int i = 1; i <= (data.length + t); i++) {
            BigDecimal decimal = BigDecimal.valueOf(gm(data, i));
            res[i-1] = decimal.setScale(2, RoundingMode.HALF_UP).doubleValue();
        }
        return res;
    }
    //相对误差(残差)
    public static double[] relativeError(double[] data, double[] res){
        double[] list = new double[data.length];
        for (int i = 0; i < data.length; i++) {
            BigDecimal decimal = BigDecimal.valueOf(Math.abs((data[i] - res[i]) / data[i]));
            list[i] = decimal.setScale(4, RoundingMode.HALF_UP).doubleValue();
        }
        return list;
    }

    //关联度检验
    public static double correlationDegree(double[] data, double[] res){
        double[] values = new double[data.length];
        for (int i = 0; i < data.length; i++) {
            values[i] = Math.abs(data[i] - res[i]);
        }
        double sum = 0.0;
        double max = Arrays.stream(values).parallel().max().getAsDouble();
        double min = Arrays.stream(values).parallel().min().getAsDouble();
        for (double value : values) {
            sum += (min + 0.5 * max) / (value + 0.5 * max);
        }
        BigDecimal decimal = BigDecimal.valueOf(sum / values.length);
        return decimal.setScale(4, RoundingMode.HALF_UP).doubleValue();
    }


    //平均相对误差
    public static double meanSquareDeviation(double[] data, double[] res){
        double sum = 0.0;
        for (int i = 0; i < data.length; i++) {
            sum += Math.abs(data[i] - res[i]) / data[i];
        }
        BigDecimal decimal = BigDecimal.valueOf(sum / data.length);
        return decimal.setScale(4, RoundingMode.HALF_UP).doubleValue();
    }

    //后验差比值
    public static double smallErrorProbability(double[] data, double[] res){
        double sum1 = 0.0, sum2 = 0.0;
        int n = data.length;
        double[] e = new double[n]; //残差序列
        for (int i = 0; i < n; i++) {
            e[i] = data[i] - res[i];
        }
        double avgData = Arrays.stream(data).parallel().reduce(0.0, Double::sum) / n;
        double avgE = Arrays.stream(e).parallel().reduce(0.0, Double::sum) / n;
        double S1 = 0.0,S2 = 0.0;
        for (int i = 0; i < n; i++) {
            S1 += Math.pow((data[i] - avgData),2);
            S2 += Math.pow((e[i] - avgE),2);
        }
        S1 = (S1 / n);
        S2 = (S2 / n);
        BigDecimal decimal = BigDecimal.valueOf(S2/S1);
        return decimal.setScale(4, RoundingMode.HALF_UP).doubleValue();
    }

    //灰色时间序列预测模型主体
    /**
     * 灰色时间序列预测模型主体
     * 参数：data输入序列；t序号
     * 返回值：res级比校验结果
     */
    private static double gm(double[] data, int t){
        //数据处理：
        int size = data.length;
        double[] arr1 = ago(data); //一次累加AGO数组
        double[] arr2 = nearestMean(arr1); //紧邻均值数组
        int tsize = arr2.length;
        //模型主体：
        //建立向量B和YN求解待估参数向量，求参数a,b
        double[][] B = new double[tsize][2];
        for (int i = 0; i < tsize; i++) {
            for (int j = 0; j < 2; j++) {
                if (j==1) B[i][j] = 1;
                else B[i][j] = -arr2[i];
            }
        }

        double[][] yn = new double[tsize][1];
        for (int i = 0; i < tsize; i++) {
            for (int j = 0; j < 1; j++) {
                yn[i][j] = data[i+1];
            }
        }
        //b转置
        double[][] bt = new double[2][tsize];
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < tsize; j++) {
                bt[i][j] = B[j][i];
            }
        }
        //b和bt相乘
        double[][] b2t = new double[2][2];
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                for (int k = 0; k < tsize; k++) {
                    b2t[i][j] += bt[i][k] * B[k][j];
                }
            }
        }
        //b2t逆矩阵
        double[][] b2tt = new double[2][2];
        b2tt[0][0] = (1/(b2t[0][0] * b2t[1][1] - b2t[0][1] * b2t[1][0])) * b2t[1][1];
        b2tt[0][1] = (1/(b2t[0][0] * b2t[1][1] - b2t[0][1] * b2t[1][0])) * (-b2t[0][1]);
        b2tt[1][0] = (1/(b2t[0][0] * b2t[1][1] - b2t[0][1] * b2t[1][0])) * (-b2t[1][0]);
        b2tt[1][1] = (1/(b2t[0][0] * b2t[1][1] - b2t[0][1] * b2t[1][0])) * b2t[0][0];
        //A矩阵
        double[][] A = new double[2][tsize];
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < tsize; j++) {
                for (int k = 0; k < 2; k++) {
                    A[i][j] += b2tt[i][k] * bt[k][j];
                }
            }
        }
        //A与yn的乘积，为c矩阵2*1
        double[][] c = new double[2][1];
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 1; j++) {
                for (int k = 0; k < tsize; k++) {
                    c[i][j] += A[i][k] * yn[k][j];
                }
            }
        }
        double a = c[0][0], b = c[1][0];
        //返回预测值
        return (data[0] - b / a) * Math.exp(-a * (t + 1)) - (data[0] - b / a) * Math.exp(-a * t);
    }


    //累加处理原始数据(AGO)
    private static double[] ago(double[] arr){
        double[] res = new double[arr.length]; //一次累加AGO数组
        double sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i];
            res[i] = sum;
        }
        return res;
    }

    //等权邻值生成数
    private static double[] nearestMean(double[] arr){
        double[] res = new double[arr.length - 1];
        for (int i = 0; i < arr.length - 1 ; i++) {
            res[i] = (arr[i] + arr[i+1]) / 2;
        }
        return res;
    }

    /**
     * 原始数据级比校验
     * 参数：arr原始数据
     * 返回值：res级比校验结果
     */
    public static String[] verify(double[] arr){
        int n = arr.length;
        String[] res = new String[n-1];
        for (int i = 1; i < n; i++) {
            double k = arr[i - 1] / arr[i];
            StringBuilder sb = new StringBuilder();
            if (k >= Math.exp(-2 / ((double) n + 1)) && k <= Math.exp(2 / ((double) n + 1))) {
                BigDecimal decimal = BigDecimal.valueOf(k);
                sb.append("第（").append(i).append("，").append(i+1)
                        .append("）个数据的级比校验结果为：")
                        .append(decimal.setScale(3,RoundingMode.HALF_UP).doubleValue())
                        .append("  达到模型标准");
            } else {
                BigDecimal decimal = BigDecimal.valueOf(k);
                sb.append("第（").append(i).append("，").append(i+1)
                        .append("）个数据的级比校验结果为：")
                        .append(decimal.setScale(3,RoundingMode.HALF_UP).doubleValue())
                        .append("  未达到模型标准！");
            }
            res[i-1] = sb.toString();
        }
        return res;
    }
}
